checkout: Merge union/add logic for copies during checkout
authorColin Walters <walters@verbum.org>
Tue, 18 Apr 2017 16:41:40 +0000 (12:41 -0400)
committerAtomic Bot <atomic-devel@projectatomic.io>
Tue, 25 Apr 2017 13:52:35 +0000 (13:52 +0000)
We really have an astonishing variety of similar functions which write files and
symlinks. I was working on a different PR and the duplication between the
union-mode and add-mode/none-mode checkout functions bothered me.

I realized that the "handle EEXIST" tri-state maps directly to the
`GLnxLinkTmpfileReplaceMode`, so deduping things makes even more sense.

Closes: #801
Approved by: jlebon

Makefile-tests.am
src/libostree/ostree-repo-checkout.c
tests/test-basic-root.sh [new file with mode: 0755]

index 8389331d96934cb1171aba56f8f9d4932509dc8a..d2059e3acd26bc0b893dd77b302de83995e192b0 100644 (file)
@@ -48,6 +48,7 @@ dist_test_scripts = \
        tests/test-basic.sh \
        tests/test-basic-user.sh \
        tests/test-basic-user-only.sh \
+       tests/test-basic-root.sh \
        tests/test-pull-subpath.sh \
        tests/test-archivez.sh \
        tests/test-remote-add.sh \
index 4de6caf9d30542e371481a36eb1026d95690e9a1..24bde4a4ad768b0ee202610b9cf9fc996515bf94 100644 (file)
@@ -154,129 +154,66 @@ write_regular_file_content (OstreeRepo            *self,
   return TRUE;
 }
 
+/*
+ * Create a copy of a file, supporting optional union/add behavior.
+ */
 static gboolean
-checkout_file_from_input_at (OstreeRepo     *self,
-                             OstreeRepoCheckoutAtOptions *options,
-                             GFileInfo      *file_info,
-                             GVariant       *xattrs,
-                             GInputStream   *input,
-                             int             destination_dfd,
-                             const char     *destination_name,
-                             GCancellable   *cancellable,
-                             GError        **error)
+create_file_copy_from_input_at (OstreeRepo     *repo,
+                                OstreeRepoCheckoutAtOptions  *options,
+                                GFileInfo      *file_info,
+                                GVariant       *xattrs,
+                                GInputStream   *input,
+                                int             destination_dfd,
+                                const char     *destination_name,
+                                GCancellable   *cancellable,
+                                GError        **error)
 {
-  int res;
+  g_autofree char *temp_filename = NULL;
+  const gboolean union_mode = options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
+  const gboolean add_mode = options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES;
 
   if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK)
     {
-      do
-        res = symlinkat (g_file_info_get_symlink_target (file_info),
-                         destination_dfd, destination_name);
-      while (G_UNLIKELY (res == -1 && errno == EINTR));
-      if (res == -1)
+      if (symlinkat (g_file_info_get_symlink_target (file_info),
+                     destination_dfd, destination_name) < 0)
         {
-          if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES)
+          /* Handle union/add behaviors if we get EEXIST */
+          if (errno == EEXIST && union_mode)
             {
-              /* Note early return */
-              return TRUE;
+              /* Unioning?  Let's unlink and try again */
+              (void) unlinkat (destination_dfd, destination_name, 0);
+              if (symlinkat (g_file_info_get_symlink_target (file_info),
+                             destination_dfd, destination_name) < 0)
+                return glnx_throw_errno (error);
             }
+          else if (errno == EEXIST && add_mode)
+            /* Note early return - we don't want to set the xattrs below */
+            return TRUE;
           else
             return glnx_throw_errno (error);
         }
 
+      /* Process ownership and xattrs now that we made the link */
       if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER)
         {
-          if (G_UNLIKELY (fchownat (destination_dfd, destination_name,
-                                    g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
-                                    g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
-                                    AT_SYMLINK_NOFOLLOW) == -1))
-            return glnx_throw_errno (error);
-
-          if (xattrs)
-            {
-              if (!glnx_dfd_name_set_all_xattrs (destination_dfd, destination_name,
-                                                   xattrs, cancellable, error))
-                return FALSE;
-            }
-        }
-    }
-  else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
-    {
-      g_autoptr(GOutputStream) temp_out = NULL;
-      int fd;
-      guint32 file_mode;
-
-      file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
-      /* Don't make setuid files on checkout when we're doing --user */
-      if (options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
-        file_mode &= ~(S_ISUID|S_ISGID);
-
-      do
-        fd = openat (destination_dfd, destination_name, O_WRONLY | O_CREAT | O_EXCL, file_mode);
-      while (G_UNLIKELY (fd == -1 && errno == EINTR));
-      if (fd == -1)
-        {
-          if (errno == EEXIST && options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES)
-            {
-              /* Note early return */
-              return TRUE;
-            }
-          else
-            return glnx_throw_errno (error);
-        }
-      temp_out = g_unix_output_stream_new (fd, TRUE);
-      fd = -1; /* Transfer ownership */
-
-      if (!write_regular_file_content (self, options, temp_out, file_info, xattrs, input,
-                                       cancellable, error))
-        return FALSE;
-    }
-  else
-    g_assert_not_reached ();
-
-  return TRUE;
-}
-
-/*
- * This function creates a file under a temporary name, then rename()s
- * it into place.  This implements union-like behavior.
- */
-static gboolean
-checkout_file_unioning_from_input_at (OstreeRepo     *repo,
-                                      OstreeRepoCheckoutAtOptions  *options,
-                                      GFileInfo      *file_info,
-                                      GVariant       *xattrs,
-                                      GInputStream   *input,
-                                      int             destination_dfd,
-                                      const char     *destination_name,
-                                      GCancellable   *cancellable,
-                                      GError        **error)
-{
-  g_autofree char *temp_filename = NULL;
-
-  if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK)
-    {
-      if (!_ostree_make_temporary_symlink_at (destination_dfd,
-                                              g_file_info_get_symlink_target (file_info),
-                                              &temp_filename,
-                                              cancellable, error))
-        return FALSE;
-
-      if (xattrs)
-        {
-          if (!glnx_dfd_name_set_all_xattrs (destination_dfd, temp_filename,
-                                               xattrs, cancellable, error))
+          if (TEMP_FAILURE_RETRY (fchownat (destination_dfd, destination_name,
+                                            g_file_info_get_attribute_uint32 (file_info, "unix::uid"),
+                                            g_file_info_get_attribute_uint32 (file_info, "unix::gid"),
+                                            AT_SYMLINK_NOFOLLOW)) == -1)
+            return glnx_throw_errno_prefix (error, "fchownat");
+
+          if (xattrs != NULL &&
+              !glnx_dfd_name_set_all_xattrs (destination_dfd, destination_name,
+                                             xattrs, cancellable, error))
             return FALSE;
         }
-      if (G_UNLIKELY (renameat (destination_dfd, temp_filename,
-                                destination_dfd, destination_name) == -1))
-        return glnx_throw_errno (error);
     }
   else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
     {
       glnx_fd_close int temp_fd = -1;
       g_autoptr(GOutputStream) temp_out = NULL;
       guint32 file_mode;
+      GLnxLinkTmpfileReplaceMode replace_mode;
 
       file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
       /* Don't make setuid files on checkout when we're doing --user */
@@ -293,7 +230,15 @@ checkout_file_unioning_from_input_at (OstreeRepo     *repo,
                                        cancellable, error))
         return FALSE;
 
-      if (!glnx_link_tmpfile_at (destination_dfd, GLNX_LINK_TMPFILE_REPLACE,
+      /* The add/union/none behaviors map directly to GLnxLinkTmpfileReplaceMode */
+      if (add_mode)
+        replace_mode = GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST;
+      else if (union_mode)
+        replace_mode = GLNX_LINK_TMPFILE_REPLACE;
+      else
+        replace_mode = GLNX_LINK_TMPFILE_NOREPLACE;
+
+      if (!glnx_link_tmpfile_at (destination_dfd, replace_mode,
                                  temp_fd, temp_filename, destination_dfd,
                                  destination_name,
                                  error))
@@ -565,22 +510,11 @@ checkout_one_file_at (OstreeRepo                        *repo,
                                   cancellable, error))
         return FALSE;
 
-      if (options->overwrite_mode == OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES)
-        {
-          if (!checkout_file_unioning_from_input_at (repo, options, source_info, xattrs, input,
-                                                     destination_dfd,
-                                                     destination_name,
-                                                     cancellable, error))
-            return g_prefix_error (error, "Union checkout of %s to %s: ", checksum, destination_name), FALSE;
-        }
-      else
-        {
-          if (!checkout_file_from_input_at (repo, options, source_info, xattrs, input,
-                                            destination_dfd,
-                                            destination_name,
-                                            cancellable, error))
-            return g_prefix_error (error, "Checkout of %s to %s: ", checksum, destination_name), FALSE;
-        }
+      if (!create_file_copy_from_input_at (repo, options, source_info, xattrs, input,
+                                           destination_dfd,
+                                           destination_name,
+                                           cancellable, error))
+        return g_prefix_error (error, "Copy checkout of %s to %s: ", checksum, destination_name), FALSE;
 
       if (input)
         {
diff --git a/tests/test-basic-root.sh b/tests/test-basic-root.sh
new file mode 100755 (executable)
index 0000000..a2fa880
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters <walters@verbum.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+id=$(id -u)
+
+if test ${id} != 0; then
+    skip "continued basic tests must be run as root (possibly in a container)"
+fi
+
+setup_test_repository "bare"
+
+echo "1..1"
+
+nextid=$(($id + 1))
+
+rm checkout-test2 -rf
+$OSTREE checkout test2 checkout-test2
+$OSTREE commit ${COMMIT_ARGS} -b test2 --tree=ref=test2 --owner-uid=$nextid
+$OSTREE ls test2 baz/cow > ls.txt
+assert_file_has_content ls.txt '-00644 '${nextid}' '${id}
+# As bare and running as root (e.g. Docker container), do some ownership tests
+# https://github.com/ostreedev/ostree/pull/801
+# Both hardlinks and copies should respect ownership, but we don't have -C yet;
+# add it when we do.
+for opt in -H; do
+    rm test2-co -rf
+    $OSTREE checkout ${opt} test2 test2-co
+    assert_streq "$(stat -c '%u' test2-co/baz/cow)" ${nextid}
+    assert_streq "$(stat -c '%u' test2-co/baz/alink)" ${nextid}
+done
+rm test2-co -rf
+# But user mode doesn't
+$OSTREE checkout -U test2 test2-co
+assert_streq "$(stat -c '%u' test2-co/baz/cow)" ${id}
+assert_streq "$(stat -c '%u' test2-co/baz/alink)" ${id}
+echo "ok ownership"